﻿namespace LiveLabs.Logging.Logs
{
    using System;
    using System.IO;
    using LiveLabs.Logging.Util;
    using Xunit;

    public class TimedRollingLogFixture : FixtureUsingFiles
    {
        [Fact]
        public void Should_Create_Utc_Hourly_File()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                Clock.SetUtcNow(date);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Utc))
                {
                    // do nothing
                }

                Assert.True(File.Exists(fileName));
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Use_Clock_UtcNow_For_Time()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                bool utcNowExecuted = false;
                Func<DateTime> provider = () =>
                {
                    utcNowExecuted = true;
                    return date;
                };
                Clock.SetUtcNowProvider(provider);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Utc))
                {
                    // do nothing
                }

                Assert.True(utcNowExecuted);
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Create_Local_Hourly_File()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Local);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                Clock.SetNow(date);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Local))
                {
                    // do nothing
                }

                Assert.True(File.Exists(fileName));
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Use_Clock_Now_For_Time()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Local);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                bool nowExecuted = false;
                Func<DateTime> provider = () =>
                {
                    nowExecuted = true;
                    return date;
                };
                Clock.SetNowProvider(provider);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Local))
                {
                    // do nothing
                }

                Assert.True(nowExecuted);
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Create_Daily_Utc_File()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd");

            Action test = () =>
            {
                Clock.SetUtcNow(date);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Daily, TimedRollingTimeBasis.Utc))
                {
                    // do nothing
                }

                Assert.True(File.Exists(fileName));
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Create_Daily_Local_File()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Local);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd");

            Action test = () =>
            {
                Clock.SetNow(date);

                using (new TimedRollingLog(baseName, TimedRollingSchedule.Daily, TimedRollingTimeBasis.Local))
                {
                    // do nothing
                }

                Assert.True(File.Exists(fileName));
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Append_To_Existing_Hourly_File()
        {
            DateTime date = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);

            const string baseName = "timed_rolling.log";
            string fileName = baseName + "." + date.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                Clock.SetUtcNow(date);

                File.WriteAllLines(fileName, new[] { "hello world", "bugger" });

                using (TimedRollingLog log = new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Utc))
                {
                    log.WriteLine("from log", LogLevel.Inform);
                }

                string[] lines = File.ReadAllLines(fileName);
                Assert.Equal(3, lines.Length);
                Assert.Contains("hello world", lines);
                Assert.Contains("bugger", lines);
                Assert.Contains("from log", lines);
            };

            CleanupFileAndResetClock(test, fileName);
        }

        [Fact]
        public void Should_Roll_File_At_Hourly_Boundary()
        {
            DateTime date1 = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);
            DateTime date2 = date1.AddHours(1);

            const string baseName = "timed_rolling.log";
            string fileName1 = baseName + "." + date1.ToString("yyyy-MM-dd-HH");
            string fileName2 = baseName + "." + date2.ToString("yyyy-MM-dd-HH");

            Action test = () =>
            {
                Clock.SetUtcNow(date1);

                using (TimedRollingLog log = new TimedRollingLog(baseName, TimedRollingSchedule.Hourly, TimedRollingTimeBasis.Utc))
                {
                    log.WriteLine("from log", LogLevel.Inform);

                    // an hour passes...
                    Clock.SetUtcNow(date2);

                    log.WriteLine("from log2", LogLevel.Inform);
                }

                Assert.True(File.Exists(fileName1));
                Assert.True(File.Exists(fileName2));

                string[] linesFrom1 = File.ReadAllLines(fileName1);
                Assert.Equal(1, linesFrom1.Length);
                Assert.Contains("from log", linesFrom1);

                string[] linesFrom2 = File.ReadAllLines(fileName2);
                Assert.Equal(1, linesFrom2.Length);
                Assert.Contains("from log2", linesFrom2);
            };

            CleanupFilesAndResetClock(test, fileName1, fileName2);
        }

        [Fact]
        public void Should_Roll_File_At_Daily_Boundary()
        {
            DateTime date1 = new DateTime(2008, 2, 1, 16, 10, 00, 00, DateTimeKind.Utc);
            DateTime date2 = date1.AddDays(1);

            const string baseName = "timed_rolling.log";
            string fileName1 = baseName + "." + date1.ToString("yyyy-MM-dd");
            string fileName2 = baseName + "." + date2.ToString("yyyy-MM-dd");

            Action test = () =>
            {
                Clock.SetUtcNow(date1);

                using (TimedRollingLog log = new TimedRollingLog(baseName, TimedRollingSchedule.Daily, TimedRollingTimeBasis.Utc))
                {
                    log.WriteLine("from log", LogLevel.Inform);

                    // a day passes...
                    Clock.SetUtcNow(date2);

                    log.WriteLine("from log2", LogLevel.Inform);
                }

                Assert.True(File.Exists(fileName1));
                Assert.True(File.Exists(fileName2));

                string[] linesFrom1 = File.ReadAllLines(fileName1);
                Assert.Equal(1, linesFrom1.Length);
                Assert.Contains("from log", linesFrom1);

                string[] linesFrom2 = File.ReadAllLines(fileName2);
                Assert.Equal(1, linesFrom2.Length);
                Assert.Contains("from log2", linesFrom2);
            };

            CleanupFilesAndResetClock(test, fileName1, fileName2);
        }

        private void CleanupFileAndResetClock(Action test, string fileName)
        {
            try
            {
                CleanupFileIfExistsAfterTest(test, fileName);
            }
            finally
            {
                Clock.Reset();
            }
        }

        private void CleanupFilesAndResetClock(Action test, params string[] fileNames)
        {
            try
            {
                CleanupFilesIfExistAfterTest(test, fileNames);
            }
            finally
            {
                Clock.Reset();
            }
        }
    }
}
